package com.biwin.module.security.starter.config;

import cn.hutool.core.collection.ListUtil;
import com.biwin.core.main.property.BiWinLoginProperties;
import com.biwin.core.main.property.BiWinProperties;
import com.biwin.module.jwt.starter.filter.BwRedisSessionFilter;
import com.biwin.module.jwt.starter.strategy.BwJwtTokenAuthenticationStrategy;
import com.biwin.module.org.api.service.ISystemUserService;
import com.biwin.module.orm.starter.factory.BasicRepositoryFactoryBean;
import com.biwin.module.security.api.properties.BiWinSecurityProperties;
import com.biwin.module.security.starter.filter.BwUsernamePasswordAuthenticationFilter;
import com.biwin.module.security.starter.handler.*;
import com.biwin.module.security.starter.service.impl.BwUserDetailServiceImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.PostConstruct;

/**
 * Security Module Auto Configuration
 *
 * @author biwin
 * @since 0.0.1
 */
@Configuration
@EnableConfigurationProperties({
        BiWinProperties.class,
        BiWinSecurityProperties.class})
@EnableJpaRepositories(
        basePackages = {"com.biwin.module.security.api.repository"},
        repositoryFactoryBeanClass = BasicRepositoryFactoryBean.class// 指定自己的工厂类
)
@ComponentScan(basePackages = "com.biwin.module.security.starter")
public class SecurityAutoConfiguration extends WebSecurityConfigurerAdapter {

    private Log log = LogFactory.getLog(this.getClass());

    @Autowired
    private BiWinProperties biWinProperties;
    @Autowired
    private BiWinLoginProperties loginProperties;
    @Autowired
    private BiWinSecurityProperties securityProperties;
    @Autowired
    private ISystemUserService systemUserService;
    @Autowired
    private BwAccessDecisionManager accessDecisionManager;
    @Autowired
    private BwFilterInvocationSecurityMetadataSource securityMetadataSource;
    @Autowired
    private BwLogoutSuccessHandler logoutSuccessHandler;
    @Autowired
    private BwAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private BwAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private BwAccessDeniedHandler accessDeniedHandler;
    @Autowired
    private BwAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private BwJwtTokenAuthenticationStrategy jwtTokenAuthenticationStrategy;
    @Autowired
    private BwRedisSessionFilter redisSessionFilter;
    @Autowired
    private BwSecurityInterceptor securityInterceptor;

    @PostConstruct
    public void init() {
        log.info("开始加载 BiWin - Security 模块...");
    }

    @Bean
    public BwUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
        BwUsernamePasswordAuthenticationFilter filter = new BwUsernamePasswordAuthenticationFilter(systemUserService,
                securityProperties, loginProperties, jwtTokenAuthenticationStrategy);
        filter.setAuthenticationManager(super.authenticationManagerBean());
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(authenticationFailureHandler);
        return filter;
    }

    @Override
    public UserDetailsService userDetailsService() {
        return new BwUserDetailServiceImpl(systemUserService, securityProperties);
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(ListUtil.toList(securityProperties.getCorsHosts()));
        configuration.setAllowedMethods(ListUtil.toList(securityProperties.getCorsMethods()));
        configuration.setAllowedHeaders(ListUtil.toList(securityProperties.getCorsHeaders()));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .headers().frameOptions().disable()
                .and()
                //开启跨域访问
                .cors()
                .and()
                //开启模拟请求，比如API POST测试工具的测试，不开启时，API POST为报403错误
                .csrf().disable()
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(accessDecisionManager);//决策管理器
                        o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
                        return o;
                    }
                })
                .and()
                //登出
                .logout()
                .logoutUrl(loginProperties.getCustomLogoutUrl())
                //允许所有用户
                .permitAll()
                //登出成功处理逻辑
                .logoutSuccessHandler(logoutSuccessHandler)
                //登出之后删除cookie
                .deleteCookies("JSESSIONID")
                //登入
                .and()
                .formLogin()
                //                .usernameParameter(loginProperties.getUsername())
                //                .passwordParameter(loginProperties.getPassword())
                .loginProcessingUrl(loginProperties.getCustomLoginUrl())
                //允许所有用户
                .permitAll()
                //登录成功处理逻辑
                .successHandler(authenticationSuccessHandler)
                //登录失败处理逻辑
                .failureHandler(authenticationFailureHandler)
                //异常处理(权限拒绝、登录失效等)
                .and()
                .exceptionHandling()
                //权限拒绝处理逻辑
                .accessDeniedHandler(accessDeniedHandler)
                //匿名用户访问无权限资源时的异常处理
                .authenticationEntryPoint(authenticationEntryPoint)
                //会话管理
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //                .sessionAuthenticationStrategy(jwtTokenSessionAuthenticationStrategy)
        //同一账号同时登录最大用户数
        //                .maximumSessions(1)
        //会话失效(账号被挤下线)处理逻辑
        //                .expiredSessionStrategy(sessionInformationExpiredStrategy);
        http.addFilterBefore(redisSessionFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
    }
}
